﻿using System;

namespace Demo.Api.Data
{
    /// <summary>
    ///     Id产生器
    /// </summary>
    public sealed class IdWorker
    {
        /*
         * 因为JS的Number类型精度最高只有53bit, 导致JS其最大安全值只有2^53 = ‭9007199254740992
         * 缩短算法后空间划分
         * 1, 高位32bit作为秒级时间戳, 时间戳减去固定值(2019年时间戳), 最高可支持到2106年(1970 + 2^32 / 3600 / 24 / 365 ≈ 2106)
         * 2, 5bit作为机器标识, 最高可部署32台机器
         * 3, 最后16bit作为自增序列, 单节点最高每秒 2^16 = 65536个ID
         */
        private static readonly DateTime Jan1St1970 = new DateTime
            (1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
        /// <summary>
        /// 项目开始时间戳(毫秒)
        /// </summary>
        private const long StartEpoch = 1645106946;
        private const int WorkerIdBits = 3; //机器Id所占的位数
        private const int AreaIdBits = 2; //一组机器的id(类似机房)
        private const int SequenceBits = 16; //序列在id中占的位数

        private const long
            MaxWorkerId = -1L ^ (-1L << WorkerIdBits); // 支持的最大机器id，结果是31 (这个移位算法可以很快的计算出几位二进制数所能表示的最大十进制数)

        private const long MaxDatacenterId = -1L ^ (-1L << AreaIdBits); // 支持的最大机房标识id，结果是31


        private const int WorkerIdShift = SequenceBits; // 机器ID向左移多少位
        private const int DatacenterIdShift = SequenceBits + WorkerIdBits; //机器Id向左移动多少位
        private const int TimestampLeftShift = SequenceBits + WorkerIdBits + AreaIdBits; //机组Id向左移动多少位
        private const long SequenceMask = -1L ^ (-1L << SequenceBits); //生成序列的掩码，这里为4095 (0b111111111111=0xfff=4095)

        private long _lastTimestamp = -1L; //上次生成ID的时间截


        /// <summary>
        ///     工作机器Id
        /// </summary>
        public long WorkerId { get; }

        /// <summary>
        ///     区域Id
        /// </summary>
        public long AreaId { get; }
        /// <summary>
        ///     构造函数
        /// </summary>
        /// <param name="workerId">机器Id</param>
        /// <param name="areaId"></param>
        /// <param name="sequence"></param>
        public IdWorker(long areaId, long workerId, long sequence = 0L)
        {
            if (workerId > MaxWorkerId || workerId < 0)
                throw new CustomException($"机器Id必须大于0小于{MaxWorkerId}");

            if (areaId > MaxDatacenterId || areaId < 0)
                throw new CustomException($"区域Id必须大于0小于{MaxDatacenterId}");
            WorkerId = workerId;
            AreaId = areaId;
            Sequence = sequence;
        }



        /// <summary>
        ///     序列号，用来记录同毫秒内产生的不同id
        /// </summary>
        public long Sequence { get; internal set; }

        private readonly object _lock = new object();

        /// <summary>
        ///     获取一个ID=d
        /// </summary>
        /// <returns></returns>
        private long GetNextId()
        {
            lock (_lock)
            {
                var timestamp = TimeGen();

                if (timestamp < _lastTimestamp) //如果当前时间小于上一次ID生成的时间戳，说明系统时钟回退过这个时候应当抛出异常
                    throw new CustomException($"时钟回退。  产生 id  {_lastTimestamp - timestamp} milliseconds");
                //如果是同一时间生成的，则进行毫秒内序列
                if (_lastTimestamp == timestamp)
                {
                    Sequence = (Sequence + 1) & SequenceMask;
                    if (Sequence == 0) //毫秒内序列溢出
                        timestamp = TilNextMillis(_lastTimestamp);
                }
                else
                {
                    Sequence = 0; //时间戳改变，毫秒内序列重置
                }

                //上次生成ID的时间截
                _lastTimestamp = timestamp;
                //移位并通过或运算拼到一起组成53位的ID
                return ((timestamp - StartEpoch) << TimestampLeftShift) |
                       (AreaId << DatacenterIdShift) |
                       (WorkerId << WorkerIdShift) | Sequence;
            }
        }

        /// <summary>
        ///     阻塞到下一个毫秒，直到获得新的时间戳
        /// </summary>
        /// <param name="lastTimestamp">上次生成ID的时间截</param>
        /// <returns></returns>
        private long TilNextMillis(long lastTimestamp)
        {
            var timestamp = TimeGen();
            while (timestamp <= lastTimestamp) timestamp = TimeGen();
            return timestamp;
        }

        /// <summary>
        ///     返回以秒为单位的当前时间
        /// </summary>
        /// <returns></returns>
        private static long TimeGen()
        {
            return (long)(DateTime.UtcNow - Jan1St1970).TotalSeconds;
        }

        private static readonly IdWorker Instance;
        static IdWorker()
        {
            Instance = new IdWorker(1, 1);
        }

        public static long NextId()
        {
            return Instance.GetNextId();
        }
    }
}